msg_tool\scripts\cat_system\image/
hg3.rs

1//! CatSystem2 HG3 Image File (.hg3)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::bit_stream::*;
6use crate::utils::img::*;
7use crate::utils::struct_pack::*;
8use anyhow::Result;
9use flate2::{Decompress, FlushDecompress};
10use msg_tool_macro::*;
11use overf::wrapping;
12use std::collections::HashMap;
13use std::io::{Read, Seek, Write};
14
15#[derive(Debug)]
16/// Builder for CatSystem2 HG3 Image scripts.
17pub struct Hg3ImageBuilder {}
18
19impl Hg3ImageBuilder {
20    /// Creates a new instance of `Hg3ImageBuilder`.
21    pub const fn new() -> Self {
22        Hg3ImageBuilder {}
23    }
24}
25
26impl ScriptBuilder for Hg3ImageBuilder {
27    fn default_encoding(&self) -> Encoding {
28        Encoding::Cp932
29    }
30
31    fn build_script(
32        &self,
33        data: Vec<u8>,
34        _filename: &str,
35        _encoding: Encoding,
36        _archive_encoding: Encoding,
37        config: &ExtraConfig,
38        _archive: Option<&Box<dyn Script>>,
39    ) -> Result<Box<dyn Script>> {
40        Ok(Box::new(Hg3Image::new(data, config)?))
41    }
42
43    fn extensions(&self) -> &'static [&'static str] {
44        &["hg3"]
45    }
46
47    fn script_type(&self) -> &'static ScriptType {
48        &ScriptType::CatSystemHg3
49    }
50
51    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
52        if buf_len >= 4 && &buf[0..4] == b"HG-3" {
53            return Some(255);
54        }
55        None
56    }
57}
58
59#[derive(Debug, Clone, StructPack, StructUnpack)]
60struct Hg3Entry {
61    header_size: u32,
62    _unk: u32,
63    width: u32,
64    height: u32,
65    bpp: u32,
66    offset_x: u32,
67    offset_y: u32,
68    canvas_width: u32,
69    canvas_height: u32,
70}
71
72#[derive(Debug)]
73/// CatSystem2 HG3 Image script.
74pub struct Hg3Image {
75    data: MemReader,
76    entries: Vec<(Hg3Entry, usize, usize)>,
77    draw_canvas: bool,
78}
79
80impl Hg3Image {
81    /// Creates a new instance of `Hg3Image` from a buffer.
82    ///
83    /// * `buf` - The buffer containing the script data.
84    /// * `config` - Extra configuration options.
85    pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
86        let mut reader = MemReader::new(buf);
87        let mut magic = [0u8; 4];
88        reader.read_exact(&mut magic)?;
89        if &magic != b"HG-3" {
90            return Err(anyhow::anyhow!("Invalid HG-3 image format"));
91        }
92        let mut offset = 0xC;
93        let mut entries = Vec::new();
94        let len = reader.data.len() as u64;
95        while offset + 0x14 < len && reader.cpeek_and_equal_at(offset + 8, b"stdinfo").is_ok() {
96            let mut section_size = reader.cpeek_u32_at(offset)?;
97            if section_size == 0 {
98                section_size = (len - offset) as u32;
99            }
100            let stdinfo_size = reader.cpeek_u32_at(offset + 0x10)?;
101            if reader
102                .cpeek_and_equal_at(offset + 8 + stdinfo_size as u64, b"img")
103                .is_ok()
104            {
105                reader.pos = (offset + 16) as usize;
106                let entry = Hg3Entry::unpack(&mut reader, false, Encoding::Cp932)?;
107                entries.push((entry, (offset + 8) as usize, section_size as usize - 8));
108            }
109            offset += section_size as u64;
110        }
111        if entries.is_empty() {
112            return Err(anyhow::anyhow!("No valid entries found in HG-3 image"));
113        }
114        Ok(Hg3Image {
115            data: reader,
116            entries,
117            draw_canvas: config.cat_system_image_canvas,
118        })
119    }
120}
121
122impl Script for Hg3Image {
123    fn default_output_script_type(&self) -> OutputScriptType {
124        OutputScriptType::Json
125    }
126
127    fn default_format_type(&self) -> FormatOptions {
128        FormatOptions::None
129    }
130
131    fn is_image(&self) -> bool {
132        true
133    }
134
135    fn export_image(&self) -> Result<ImageData> {
136        if self.entries.len() > 1 {
137            eprintln!(
138                "WARN: There are multiple entries in the HG-3 image, only the first one will be exported."
139            );
140            crate::COUNTER.inc_warning();
141        }
142        let (entry, offset, size) = &self.entries[0];
143        let data = &self.data.data[*offset..*offset + *size];
144        let reader = Hg3Reader {
145            m_input: MemReaderRef::new(data),
146            m_info: entry.clone(),
147            m_pixel_size: entry.bpp / 8,
148        };
149        let mut img = reader.unpack()?;
150        if self.draw_canvas {
151            if entry.canvas_width > 0 && entry.canvas_height > 0 {
152                img = draw_on_canvas(
153                    img,
154                    entry.canvas_width,
155                    entry.canvas_height,
156                    entry.offset_x,
157                    entry.offset_y,
158                )?;
159            }
160        }
161        Ok(img)
162    }
163
164    fn is_multi_image(&self) -> bool {
165        self.entries.len() > 1
166    }
167
168    fn export_multi_image<'a>(
169        &'a self,
170    ) -> Result<Box<dyn Iterator<Item = Result<ImageDataWithName>> + 'a>> {
171        Ok(Box::new(Hg3ImageIter {
172            iter: self.entries.iter(),
173            index: 0,
174            data: self.data.to_ref(),
175            draw_canvas: self.draw_canvas,
176        }))
177    }
178}
179
180struct Hg3ImageIter<'a, T: Iterator<Item = &'a (Hg3Entry, usize, usize)> + 'a> {
181    iter: T,
182    index: usize,
183    data: MemReaderRef<'a>,
184    draw_canvas: bool,
185}
186
187impl<'a, T: Iterator<Item = &'a (Hg3Entry, usize, usize)> + 'a> Iterator for Hg3ImageIter<'a, T> {
188    type Item = Result<ImageDataWithName>;
189
190    fn next(&mut self) -> Option<Self::Item> {
191        if let Some((entry, offset, size)) = self.iter.next() {
192            let data = &self.data.data[*offset..*offset + *size];
193            let reader = Hg3Reader {
194                m_input: MemReaderRef::new(data),
195                m_info: entry.clone(),
196                m_pixel_size: entry.bpp / 8,
197            };
198            self.index += 1;
199            match reader.unpack() {
200                Ok(mut img) => {
201                    if self.draw_canvas {
202                        if entry.canvas_width > 0 && entry.canvas_height > 0 {
203                            img = match draw_on_canvas(
204                                img,
205                                entry.canvas_width,
206                                entry.canvas_height,
207                                entry.offset_x,
208                                entry.offset_y,
209                            ) {
210                                Ok(canvas_img) => canvas_img,
211                                Err(e) => return Some(Err(e)),
212                            };
213                        }
214                    }
215                    Some(Ok(ImageDataWithName {
216                        name: format!("{:04}", self.index - 1),
217                        data: img,
218                    }))
219                }
220                Err(e) => Some(Err(e)),
221            }
222        } else {
223            None
224        }
225    }
226}
227
228struct Hg3Reader<'a> {
229    m_input: MemReaderRef<'a>,
230    m_info: Hg3Entry,
231    m_pixel_size: u32,
232}
233
234impl<'a> Hg3Reader<'a> {
235    fn unpack_stream(
236        &mut self,
237        data_offset: usize,
238        data_packed: usize,
239        data_unpacked: usize,
240        ctl_packed: usize,
241        ctl_unpacked: usize,
242    ) -> Result<Vec<u8>> {
243        let ctl_offset = data_offset + data_packed;
244        let mut data = Vec::with_capacity(data_unpacked);
245        data.resize(data_unpacked, 0);
246        let z = &self.m_input.data[data_offset..data_offset + data_packed];
247        let mut decompressor = Decompress::new(true);
248        decompressor.decompress(z, &mut data, FlushDecompress::Finish)?;
249        let z = &self.m_input.data[ctl_offset..ctl_offset + ctl_packed];
250        let mut ctl = Vec::with_capacity(ctl_unpacked);
251        ctl.resize(ctl_unpacked, 0);
252        let mut decompressor = Decompress::new(true);
253        decompressor.decompress(z, &mut ctl, FlushDecompress::Finish)?;
254        let mut bits = LsbBitStream::new(MemReaderRef::new(&ctl));
255        let mut copy = bits.get_next_bit()?;
256        let output_size = Self::get_bit_count(&mut bits)? as usize;
257        let mut output = Vec::with_capacity(output_size);
258        output.resize(output_size, 0);
259        let mut src = 0;
260        let mut dst = 0;
261        while dst < output_size {
262            let count = Self::get_bit_count(&mut bits)? as usize;
263            if copy {
264                output[dst..dst + count].copy_from_slice(&data[src..src + count]);
265                src += count;
266            }
267            dst += count;
268            copy = !copy;
269        }
270        Ok(self.apply_delta(&output))
271    }
272
273    fn get_bit_count(bits: &mut LsbBitStream<MemReaderRef<'_>>) -> Result<u32> {
274        let mut n = 0;
275        while !bits.get_next_bit()? {
276            n += 1;
277            if n >= 0x20 {
278                return Err(anyhow::anyhow!("Overflow at HG-3 Reader."));
279            }
280        }
281        let mut value = 1;
282        for _ in 0..n {
283            value = (value << 1) | (bits.get_next_bit()? as u32);
284        }
285        Ok(value)
286    }
287
288    fn convert_value(mut val: u8) -> u8 {
289        let carry = val & 1 != 0;
290        val >>= 1;
291        if carry { val ^ 0xff } else { val }
292    }
293
294    fn apply_delta(&self, pixels: &[u8]) -> Vec<u8> {
295        let mut table = [[0u32; 0x100]; 4];
296        for i in 0..0x100u32 {
297            let mut val = i & 0xC0;
298            val <<= 6;
299            val |= i & 0x30;
300            val <<= 6;
301            val |= i & 0x0C;
302            val <<= 6;
303            val |= i & 0x03;
304            table[0][i as usize] = val << 6;
305            table[1][i as usize] = val << 4;
306            table[2][i as usize] = val << 2;
307            table[3][i as usize] = val;
308        }
309        let pxl_len = pixels.len();
310        let plane_size = pxl_len / 4;
311        let mut plane0 = 0;
312        let mut plane1 = plane0 + plane_size;
313        let mut plane2 = plane1 + plane_size;
314        let mut plane3 = plane2 + plane_size;
315        let mut output = Vec::with_capacity(pxl_len);
316        output.resize(pxl_len, 0);
317        let mut dst = 0;
318        while dst < pxl_len {
319            let val = table[0][pixels[plane0] as usize]
320                | table[1][pixels[plane1] as usize]
321                | table[2][pixels[plane2] as usize]
322                | table[3][pixels[plane3] as usize];
323            plane0 += 1;
324            plane1 += 1;
325            plane2 += 1;
326            plane3 += 1;
327            output[dst] = Self::convert_value(val as u8);
328            dst += 1;
329            output[dst] = Self::convert_value((val >> 8) as u8);
330            dst += 1;
331            output[dst] = Self::convert_value((val >> 16) as u8);
332            dst += 1;
333            output[dst] = Self::convert_value((val >> 24) as u8);
334            dst += 1;
335        }
336        let stride = self.m_info.width * self.m_pixel_size;
337        for x in self.m_pixel_size..stride {
338            let target = x as usize - self.m_pixel_size as usize;
339            wrapping! {
340                output[x as usize] += output[target];
341            }
342        }
343        let mut prev = 0;
344        for _ in 1..self.m_info.height {
345            let line = prev + stride;
346            for x in 0..stride {
347                let src = line as usize + x as usize;
348                let target = prev as usize + x as usize;
349                wrapping! {
350                    output[src] += output[target];
351                }
352            }
353            prev = line;
354        }
355        output
356    }
357
358    fn unpack(mut self) -> Result<ImageData> {
359        self.m_input.pos = self.m_info.header_size as usize;
360        let mut image_type = [0; 8];
361        self.m_input.read_exact(&mut image_type)?;
362        if &image_type == b"img0000\0" {
363            return self.unpack_img0000();
364        } else if &image_type == b"img_jpg\0" {
365            return self.unpack_jpeg();
366        } else {
367            return Err(anyhow::anyhow!("Unsupported image type: {:?}", image_type));
368        }
369    }
370
371    fn unpack_img0000(&mut self) -> Result<ImageData> {
372        self.m_input.pos = self.m_info.header_size as usize + 0x18;
373        let packed_data_size = self.m_input.read_u32()?;
374        let data_size = self.m_input.read_u32()?;
375        let ctl_packed_size = self.m_input.read_u32()?;
376        let ctl_size = self.m_input.read_u32()?;
377        let mut data = self.unpack_stream(
378            self.m_info.header_size as usize + 0x28,
379            packed_data_size as usize,
380            data_size as usize,
381            ctl_packed_size as usize,
382            ctl_size as usize,
383        )?;
384        let expected_size =
385            self.m_info.width as usize * self.m_info.height as usize * self.m_pixel_size as usize;
386        let data_len = data.len();
387        if data_len < expected_size {
388            return Err(anyhow::anyhow!(
389                "Unpacked data size {} is less than expected size {}",
390                data.len(),
391                expected_size
392            ));
393        }
394        if data_len > expected_size {
395            if data.iter().skip(expected_size).any(|&x| x != 0) {
396                eprintln!(
397                    "WARN: Unpacked data size {} is greater than expected size {} and contains non zero data, truncating excess data.",
398                    data_len, expected_size
399                );
400                crate::COUNTER.inc_warning();
401            }
402            data.truncate(expected_size);
403        }
404        let fmt = match self.m_info.bpp {
405            24 => ImageColorType::Bgr,
406            32 => ImageColorType::Bgra,
407            _ => {
408                return Err(anyhow::anyhow!(
409                    "Unsupported BPP: {} in HG-3 image",
410                    self.m_info.bpp
411                ));
412            }
413        };
414        let mut img = ImageData {
415            width: self.m_info.width,
416            height: self.m_info.height,
417            color_type: fmt,
418            depth: 8,
419            data,
420        };
421        flip_image(&mut img)?;
422        Ok(img)
423    }
424
425    fn unpack_jpeg(&mut self) -> Result<ImageData> {
426        let toc = self.read_sections()?;
427        self.m_input.pos = (*toc
428            .get("img_jpg")
429            .ok_or(anyhow::anyhow!("Missing img_jpg section"))?)
430            as usize
431            + 12;
432        let jpeg_size = self.m_input.read_u32()?;
433        let mut data = {
434            let jpeg = StreamRegion::with_size(&mut self.m_input, jpeg_size as u64)?;
435            load_jpg(jpeg)?
436        };
437        if data.color_type.bpp(1) < 3 {
438            return Err(anyhow::anyhow!(
439                "Unsupported JPEG color type: {:?} in HG-3 image",
440                data.color_type
441            ));
442        }
443        let src_pixel_size = data.color_type.bpp(1) as usize;
444        let alpha = if let Some(&alpha_offset) = toc.get("img_al") {
445            Some(self.read_alpha(alpha_offset as u32)?)
446        } else {
447            None
448        };
449        let target_color_type = if alpha.is_some() {
450            ImageColorType::Rgba
451        } else {
452            data.color_type
453        };
454        let pixel_size = target_color_type.bpp(1) as usize;
455        let stride = self.m_info.width as usize * pixel_size;
456        let mut pixels = vec![0; stride * self.m_info.height as usize];
457        let mut src = 0;
458        let mut dst = 0;
459        let mut src_a = 0;
460        let src_g = 1;
461        let (src_b, src_r) = if toc.contains_key("imgmode") {
462            (2, 0)
463        } else {
464            (0, 2)
465        };
466        for _ in 0..self.m_info.width as usize * self.m_info.height as usize {
467            pixels[dst] = data.data[src + src_b];
468            pixels[dst + 1] = data.data[src + src_g];
469            pixels[dst + 2] = data.data[src + src_r];
470            if let Some(ref alpha_data) = alpha {
471                pixels[dst + 3] = alpha_data[src_a];
472                src_a += 1;
473            }
474            dst += pixel_size;
475            src += src_pixel_size;
476        }
477        data.data = pixels;
478        data.color_type = target_color_type;
479        Ok(data)
480    }
481
482    fn read_alpha(&mut self, start_pos: u32) -> Result<Vec<u8>> {
483        self.m_input.pos = start_pos as usize + 0x10;
484        let packed_size = self.m_input.read_u32()?;
485        let alpha_size = self.m_input.read_u32()?;
486        let alpha_in = StreamRegion::with_size(&mut self.m_input, packed_size as u64)?;
487        let mut alpha = Vec::new();
488        flate2::read::ZlibDecoder::new(alpha_in).read_to_end(&mut alpha)?;
489        if alpha.len() != alpha_size as usize {
490            return Err(anyhow::anyhow!(
491                "Alpha data size {} does not match expected size {}",
492                alpha.len(),
493                alpha_size
494            ));
495        }
496        Ok(alpha)
497    }
498
499    fn read_sections(&mut self) -> Result<HashMap<String, u32>> {
500        let mut sections = HashMap::new();
501        let mut next_offset = self.m_info.header_size;
502        loop {
503            self.m_input.pos = next_offset as usize;
504            let section_name = self.m_input.read_fstring(8, Encoding::Cp932, true)?;
505            let section_size = self.m_input.read_u32()?;
506            sections.insert(section_name, next_offset);
507            next_offset += section_size;
508            if section_size == 0 {
509                break;
510            }
511        }
512        Ok(sections)
513    }
514}